package com.xyzwps.paimon.furina.infra

import com.xyzwps.paimon.furina.common.AppErrorCode.*
import com.xyzwps.paimon.furina.common.AppException
import com.xyzwps.paimon.furina.common.QueryParams
import com.xyzwps.paimon.furina.common.log
import com.xyzwps.paimon.furina.common.value.PageSize
import jakarta.validation.Validator
import org.springframework.http.converter.HttpMessageNotReadableException
import org.springframework.web.servlet.function.ServerRequest
import org.springframework.web.servlet.function.body
import kotlin.jvm.optionals.getOrNull

internal const val ATTR_AUTHENTICATED = "attr_authenticated"
internal const val ATTR_USER_ID = "attr_user_id"
internal const val ATTR_SESSION_ID = "attr_session_id"
internal const val ATTR_VALIDATOR = "attr_validator"

val ServerRequest.authenticated: Boolean
    get() {
        val v = this.attributes()[ATTR_AUTHENTICATED] ?: return false
        return if (v is Boolean) v else false
    }

fun ServerRequest.setAuthenticated(authed: Boolean) {
    this.attributes()[ATTR_AUTHENTICATED] = authed
}

val ServerRequest.userId: Long
    get() {
        val userId = this.attributes()[ATTR_USER_ID] ?: throw AppException(NOT_AUTHENTICATED, "尚未登录")
        return if (userId is Long) userId else throw AppException(UNHANDLED)
    }

fun ServerRequest.setUserId(userId: Long) {
    this.attributes()[ATTR_USER_ID] = userId
}

fun ServerRequest.setSessionId(sessionId: String) {
    this.attributes()[ATTR_SESSION_ID] = sessionId
}

fun ServerRequest.injectValidator(validator: Validator) {
    this.attributes()[ATTR_VALIDATOR] = validator
}

fun ServerRequest.getValidator(): Validator {
    val validator = this.attributes()[ATTR_VALIDATOR] ?: throw AppException(UNHANDLED, "No validator injected")
    return if (validator is Validator) validator else throw AppException(UNHANDLED, "No validator injected")
}

inline fun <reified T : Any> ServerRequest.validatedBody(): T {
    try {
        val it = this.body<T>()
        val violations = this.getValidator().validate(it)
        if (violations.isEmpty()) return it

        throw AppException(REQUEST_VALIDATION_FAILED, violations.first().message)
    } catch (e: Throwable) {
        log.warn("request body parsing or validating error", e)
        throw when (e) {
            is AppException -> e
            is HttpMessageNotReadableException -> handleHttpMessageNotReadableException(e)
            else -> AppException(REQUEST_PARSING_FAILED)
        }
    }
}

fun ServerRequest.pathVariableLong(name: String): Long {
    val value = pathVariable(name)
    try {
        return value.toLong()
    } catch (e: NumberFormatException) {
        throw AppException(PATH_VAR_INVALID, "Path variable $name is invalid")
    }
}

fun ServerRequest.paramLong(name: String): Long? {
    val value = param(name).getOrNull() ?: return null
    if (value == "") return null

    try {
        return value.toLong()
    } catch (e: NumberFormatException) {
        throw AppException(SEARCH_PARAM_INVALID, "Search param $name is invalid")
    }
}

fun ServerRequest.paramInt(name: String): Int? {
    val value = param(name).getOrNull() ?: return null
    if (value == "") return null

    try {
        return value.toInt()
    } catch (e: NumberFormatException) {
        throw AppException(SEARCH_PARAM_INVALID, "Search param $name is invalid")
    }
}

fun ServerRequest.paramPageSize(): PageSize {
    val value = param(QueryParams.SIZE).getOrNull() ?: return PageSize.DEFAULT
    if (value == "") return PageSize.DEFAULT

    try {
        return PageSize(value.toInt())
    } catch (e: NumberFormatException) {
        throw AppException(SEARCH_PARAM_INVALID, "Page size is invalid.")
    }
}


fun handleHttpMessageNotReadableException(e: HttpMessageNotReadableException): AppException {
    var cause: Throwable? = e
    while (cause != null) {
        if (cause is AppException) return cause
        cause = cause.cause
    }
    return AppException(REQUEST_PARSING_FAILED, "Cannot deserialize request body")
}